package cn.sc.summer.gateway.model;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceInstance;
import cn.sc.summer.constant.service.Support;
import cn.sc.summer.gateway.exception.CommonException;
import cn.sc.summer.redis.util.NacosInstancesService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 类名：自定义负载均衡实现需要实现 ReactorServiceInstanceLoadBalancer 接口 以及重写choose方法
 *
 * @author a-xin
 * @date 2024/5/9 13:54
 */
@Slf4j
public abstract class NacosLoadBalanceRule implements ReactorServiceInstanceLoadBalancer, Support {

    private static final long serialVersionUID = -8067501807123708155L;

    protected static final String INSTANCE_ID = "nacos.instanceId";
    protected static final String WEIGHT = "nacos.weight";
    protected static final String CLUSTER = "nacos.cluster";
    protected static final String EPHEMERAL = "nacos.ephemeral";
    protected static final String HEALTHY = "nacos.healthy";

    /**
     * 注入当前服务的nacos的配置信息
     */
    protected final NacosDiscoveryProperties nacosDiscoveryProperties;

    /**
     * loadbalancer 提供的访问当前服务的名称
     */
    protected final String serviceId;

    /**
     * loadbalancer 提供的访问的服务列表
     */
    protected ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public NacosLoadBalanceRule(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                String serviceId,
                                NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.serviceId = serviceId;
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
    }

    /**
     * 服务器调用负载均衡时调的放啊
     * 此处代码内容与 RandomLoadBalancer 一致
     */
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map((serviceInstances) -> this.processInstanceResponse(supplier, serviceInstances));
    }

    /**
     * 对负载均衡的服务进行筛选的方法
     * 此处代码内容与 RandomLoadBalancer 一致
     */
    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstance) {
        List<ServiceInstance> serviceInstances = getServerInstance(serviceInstance);
        Response<ServiceInstance> serviceInstanceResponse =
                this.getInstanceResponse(filterInstance(serviceInstances.stream().distinct().collect(Collectors.toList())));
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    /**
     * 获取所有的服务列表，如果不添加额外服务，则不进行操作
     *
     * @param serviceInstances 当前所有的服务列表
     * @return 服务列表
     */
    protected List<ServiceInstance> getServerInstance(List<ServiceInstance> serviceInstances) {
        if (CollUtil.isEmpty(serviceInstances)) {
            return getNacosServerInstance();
        }
        return serviceInstances;
    }

    /**
     * 获取nacos上所有服务列表，作为基础服务为空时的补偿做法，一般情况不建议使用
     *
     * @return nacos所有服务列表
     */
    protected List<ServiceInstance> getNacosServerInstance() {
        List<cn.sc.summer.redis.po.ServiceInstance> allInstances = NacosInstancesService.getAllInstanceInfo(serviceId, nacosDiscoveryProperties);
        if (CollUtil.isEmpty(allInstances)) {
            throw new CommonException("-> Services [" + serviceId + "] that don't exist!");
        }
        return allInstances.stream().map(instance -> {
            NacosServiceInstance nacosServiceInstance = new NacosServiceInstance();
            nacosServiceInstance.setServiceId(instance.getServiceName().split("@@")[1]);
            nacosServiceInstance.setHost(instance.getIp());
            nacosServiceInstance.setPort(instance.getPort());
            nacosServiceInstance.setSecure(false);

            Map<String, String> metadata = instance.getMetadata();
            metadata.put(INSTANCE_ID, instance.getInstanceId());
            metadata.put(WEIGHT, instance.getWeight().toString());
            metadata.put(CLUSTER, instance.getClusterName());
            metadata.put(EPHEMERAL, instance.getEphemeral().toString());
            metadata.put(HEALTHY, instance.getHealthy().toString());
            nacosServiceInstance.setMetadata(metadata);
            return nacosServiceInstance;
        }).collect(Collectors.toList());
    }

    /**
     * 对所有的服务列表进行过滤
     *
     * @param serverInstances 所有服务信息
     * @return 过滤后的服务列表
     */
    protected abstract List<ServiceInstance> filterInstance(List<ServiceInstance> serverInstances);

    /**
     * 获取适当的服务进行执行
     *
     * @param serverInstances 所有服务信息
     * @return 过滤后的服务
     */
    protected abstract ServiceInstance getOneInstance(List<ServiceInstance> serverInstances);

    /**
     * 对负载均衡的服务进行筛选的方法
     * 此处的 instances 实例列表  只会提供健康的实例  所以不需要担心如果实例无法访问的情况
     */
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        return new DefaultResponse(getOneInstance(instances));
    }

    /**
     * Determine the types of data sources supported by this class
     *
     * @return Supported data source types
     */
    public String supports() {
        return Support.DEFAULT;
    }

}
